﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text.RegularExpressions;

using CSRedis;

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;

using Newtonsoft.Json.Linq;

using Utils.Pool;



namespace Utils.Cache.Redis
{

    public class RedisCacheStrategy : ICacheStrategy
    {

        private object _lock = new object();

        private CSRedisClient _connection;

        private const string DEFAULT_STRING = "127.0.0.1:6379,Password=abc123,DefaultDatabase=0";

        private string RedisConnectionString;

        private static ConcurrentDictionary<string, CSRedisClient> _cache = new ConcurrentDictionary<string, CSRedisClient>();

        public RedisCacheStrategy(IConfiguration configuration)
        {
            RedisConnectionString = configuration["redisConnectionStrings"].IfNullOrEmpty(DEFAULT_STRING);
            _connection = _cache.GetOrAdd(RedisConnectionString, key => new CSRedisClient(key));
        }

        private RedisCacheStrategy(string redisConnectionString, int db)
        {

            RedisConnectionString = Regex.Replace(redisConnectionString, "[Dd]efault[Dd]atabase=([0-9]{1,2})", "DefaultDatabase=" + db);
            _connection = _cache.GetOrAdd(RedisConnectionString, key => new CSRedisClient(key));
        }

        public ICacheStrategy Change(int db)
        {
            return new RedisCacheStrategy(RedisConnectionString, db);
        }

        public CSRedisClient GetDatabase()
        {
            if (_connection == null)
            {
                throw Error.DefaultError("csredis 已断开");
            }
            return _connection;
        }


        public void Add<T>(string key, T value, int seconds)
        {
            if (value == null)
            {
                return;
            }
            var expiresIn = TimeSpan.FromSeconds(seconds);
            GetDatabase().Set(key, value, expiresIn);
        }

        public T GetHash<T>(string key, string field)
        {
            return GetDatabase().HGet<T>(key, field);
        }


        public void AddHash<T>(string key, string field, T value)
        {
            if (value == null)
            {
                return;
            }
            GetDatabase().HSet(key, field, value);
        }

        public void SetHash<T>(string key, IDictionary<string, T> fields)
        {
            if (fields == null)
            {
                return;
            }
            foreach (var item in fields)
            {
                GetDatabase().HSet(key, item.Key, item.Value);
            }
        }

        public Dictionary<string, T> GetHashAll<T>(string key)
        {
            return GetDatabase().HGetAll<T>(key);
        }

        public void DeleteHash(string key, string field)
        {
            GetDatabase().HDel(key, field);
        }


        public object Get(string key)
        {
            var _db = GetDatabase();
            return _db.Get<JObject>(key);
        }


        public T Get<T>(string key)
        {
            var _db = GetDatabase();
            return _db.Get<T>(key);
        }


        public void RemoveAll()
        {
            GetDatabase().ScriptFlush();
        }

        public void Remove(string key)
        {
            GetDatabase().Del(key);
        }

        public void RemoveStartsWith(string key)
        {
            var redsKeys = GetDatabase().Keys($"{key}*");
            if (redsKeys == null || redsKeys.Length == 0)
            {
                return;
            }
            string[] redskeys = redsKeys;
            foreach (var item in redskeys)
            {
                Remove(item);
            }
        }

        public bool HasKey(string key)
        {
            return GetDatabase().Exists(key);
        }


        public void Lock(string lockKey, Action action, int millisecondsTimeout = 2000)
        {
            var database = GetDatabase();
            using (database.TryLock(lockKey, millisecondsTimeout / 1000) ?? throw Error.DefaultError("网络繁忙，请稍后再试"))
            {
                action();
            }
        }

        public T Lock<T>(string lockKey, Func<T> action, int millisecondsTimeout)
        {
            var database = GetDatabase();
            using (database.TryLock(lockKey, millisecondsTimeout / 1000) ?? throw Error.DefaultError("网络繁忙，请稍后再试"))
            {
                return action();
            }
        }

        public void Publish<T>(string channel, T value)
        {
            GetDatabase().PublishAsync(channel, Serialize(value));
        }

        public void Publish<T>(T value) where T : ISubscribeEntity
        {
            Publish(value.ChannelKey, value);
        }

        public void Subscribe<T>(Action<string, T> handler) where T : ISubscribeEntity, new()
        {
            var t = new T();
            _connection.Subscribe((t.ChannelKey, (arg) =>
            {
                try
                {
                    handler?.Invoke(arg.Channel, Deserialize<T>(arg.Body));
                }
                catch (Exception e)
                {
                    Console.WriteLine("RedisSubscribe：\r\n" + e.ToString());
                }
            }
            ));
        }

        public void Subscribe<T>(string channel, Action<string, T> handler)
        {
            _connection.Subscribe((channel, (arg) =>
            {
                handler?.Invoke(arg.Channel, Deserialize<T>(arg.Body));
            }
            ));
        }


        public void CloseChange()
        {

        }

        private string Serialize(object obj)
        {
            //return MTP.Core.MTPSerialize.Serialize(obj);
            return JsonHelper.JSONSerialize(obj);
        }

        private T Deserialize<T>(string value)
        {
            // return MTP.Core.MTPSerialize.Deserialize<T>(value);
            return JsonHelper.JSONDeserialize<T>(value);
        }
    }
}
